package com.ruoyi.network.edge;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

import javax.annotation.PreDestroy;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import com.ruoyi.network.config.NetworkComponent;
import com.ruoyi.network.util.Threads;

import lombok.extern.slf4j.Slf4j;

@Component
@ConditionalOnProperty(name = "network.edge.enable", havingValue = "true", matchIfMissing = false)
@Slf4j
public class EdgeNetwork implements NetworkComponent {
	@Value("${network.edge.public.host:47.107.226.11}") // 公网ip
	private String publicHost;
	@Value("${network.edge.public.port:8023}") // 公网端口
	private Integer publicPort;
	//@Value("${network.edge.inner.host:120.79.39.71}") // 内网ip
	private String innerHost;
	//@Value("${network.edge.inner.port:22}") // 内网端口
	private Integer innerPort;
	@Value("${network.edge.workSize:16}") // 内网端口
	private int workSize;
	private boolean isActive = false;
	private boolean isStop = false;
	private Selector selector;
	ExecutorService workPool = Executors.newSingleThreadExecutor();
	public String getInnerHost() {
		return innerHost;
	}
	public void setInnerHost(String innerHost) {
		this.innerHost = innerHost;
	}
	public Integer getInnerPort() {
		return innerPort;
	}
	public void setInnerPort(Integer innerPort) {
		this.innerPort = innerPort;
	}
	public void setActive(boolean isActive) {
		this.isActive = isActive;
	}
	// 热重启时部分资源(线程池、外部连接、退出死循环等)不一定能正常关闭,需要通过@PreDestroy手动关闭处理
	// 从Debug线程情况中可判断是否有关闭,若未关闭数量将一直增加
	@PreDestroy
    public void destroy(){
		log.info("====系统退出/热重启,关闭后台资源(线程池、外部连接、退出死循环等)====");
		isStop = true;
		EdgeHelper.distory();
		if(selector != null) {
			try {
				selector.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		Threads.shutdownAndAwaitTermination(workPool);
    }
	private void connect(Selector selector) {
		// 一个线程处理所有连接
		NPClient outerClient = null;
		NPClient innerClient = null;
		try {
			if(StringUtils.isEmpty(publicHost) || publicPort == null ||
					StringUtils.isEmpty(innerHost) || innerPort == null) {
				return;
			}
			outerClient = new NPClient("外网连接", publicHost, publicPort, selector);
			innerClient = new NPClient("内网连接", innerHost, innerPort, selector);
			EdgeContext.clients.add(outerClient);
			EdgeContext.clients.add(innerClient);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void selectedKeys(Selector selector) throws IOException {
		// int readyChannels =
		// selector.select();//阻塞直到有事件发生,会导致异步执行socketChannel.register()方法时阻塞
		int readyChannels = selector.select(100L);// 没有事件发生时,超时后必须唤醒一次,解决异步执行socketChannel.register()方法时阻塞
		if (readyChannels == 0) {
			return;
		}
		Set<SelectionKey> keys = selector.selectedKeys();
		Iterator<SelectionKey> it = keys.iterator();
		SelectionKey key = null;
		while (it.hasNext()) {
			try {
				key = it.next();
				SocketChannel sc = (SocketChannel) key.channel();
				List<NPClient> list = null;
				list = EdgeContext.clients.stream().filter((c) -> c.getSocketChannel() == sc).collect(Collectors.toList());
				if (list != null && list.size() > 0) {
					String name = list.get(0).getName();
					handleInput(key, name, selector, sc);
				}
			} catch (Exception e) {
				e.printStackTrace();
				EdgeHelper.close(null, key);

			} finally {
				it.remove();
			}
		}

	}

	private void handleInput(SelectionKey key, String name, Selector selector, SocketChannel sc) throws Exception {
		Date timestamp = new Date();
		if (key.isValid()) {
			if (key.isConnectable()) {
				if (sc.finishConnect()) {
					log.debug(name + " " + sc.getRemoteAddress() + "连接成功");
					EdgeHelper.handleConnect(name, selector, key, timestamp);
					// isAccept = true;
				} else {// 连接失败
					EdgeHelper.close(null, key);
				}
			} else if (key.isReadable()) {// 通道是否有读事件
				EdgeHelper.handleRead(name, selector, key, timestamp);

			}
		}
	}

	@Override
	public void start() {
		log.debug(EdgeNetwork.class.getName() + " 内网穿透边缘端...");
		EdgeHelper.start();
		try {
			// 创建选择器
			selector = Selector.open();
			// 每个连接使用了相同的selector处理
			for (int i = 0; i < workSize; i++) {
				if(isActive) {
					connect(selector);
				}
			}
			// 断开重连
			workPool.execute(() -> {
				while (true && !isStop) {
					List<NPClient> tmp = new LinkedList<NPClient>();
					for (NPClient client : EdgeContext.clients) {
						if (!client.getSocketChannel().isOpen()) {
							tmp.add(client);
						}
					}
					EdgeContext.clients.removeAll(tmp);
					while (EdgeContext.clients.size() < workSize * 2 && isActive) {
						connect(selector);
					}
					try {
						Thread.sleep(100L);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
			// 事件处理
			while (true && !isStop) {
				selectedKeys(selector);
			}
		} catch (Exception e) {
			e.printStackTrace();
			workPool.shutdown();
		}

	}
}
